﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Expressions;
using Infrastructure.Domain;

namespace Infrastructure.Utility
{
    public static class LinqExtensions 
    {
        public static string GetPropertyName<T, TProperty>(this T instance, Expression<Func<TProperty>> propertySelector, bool fullPath = true)
        {
            //TODO: Trick to get the property path without parsing lambda. Need to see if there any shortcoming. This needs
            //to work for nested references x.y.z
            //Also see http://msmvps.com/blogs/matthieu/archive/2008/06/06/entity-framework-include-with-func-next.aspx

            string path = propertySelector
                            .ToString()
                            .Split(new[] { "=>" }, StringSplitOptions.None)[1]
                            .Trim()
                            .Split(new[] { '.' }, 2)[1];

            if (!fullPath)
                path = path.Split(new[] { '.' }).Last();

            return path.Replace(")", String.Empty);
        }

        public static IQueryable<T> ApplyPagingOptions<T>(this IQueryable<T> query, PagingOptions options)
        {
            if (!string.IsNullOrWhiteSpace(options.Sort))
                query = query.OrderBy(options.Sort);
                
                query = query.Skip(options.StartRow).Take(options.PageSize);

            return query;
        }
        
        public static string GetName<T>(this T type, Expression<Func<T, object>> expr)
        {
            var body = ((MemberExpression)expr.Body);
            return body.Member.Name;
        }

        public static string GetMethod<T>(this T type, Expression<Func<T, Action>> expr)
        {
            var body = ((MemberExpression)expr.Body);
            return body.Member.Name;
        }

        [DebuggerStepThrough]
        public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        /// <summary>
        /// Used for combining expressions.  If first is null, second will be returned
        /// </summary>
        [DebuggerStepThrough]
        public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            if (first == null)
                return second;
            else
                return first.Compose(second, Expression.And);
        }

        /// <summary>
        /// Used for combining expressions.  If first is null, second will be returned
        /// </summary>
        [DebuggerStepThrough]
        public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            if (first == null)
                return second;
            else
                return first.Compose(second, Expression.Or);
        }

        public class ParameterRebinder : ExpressionVisitor
        {
            private readonly Dictionary<ParameterExpression, ParameterExpression> map;
            public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
            {
                this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
            }

            public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
            {
                return new ParameterRebinder(map).Visit(exp);
            }

            protected override Expression VisitParameter(ParameterExpression p)
            {
                ParameterExpression replacement;
                if (map.TryGetValue(p, out replacement))
                {
                    p = replacement;
                }
                return base.VisitParameter(p);
            }
        }
    }


        /* EF does not like invoke

        /// <summary>
        /// Used for combining expressions.  If expr1 is null, expr2 will be returned
        /// </summary>
        public static Expression<Func<T, bool>> AndAlso<T>(
                 this Expression<Func<T, bool>> expr1,
                 Expression<Func<T, bool>> expr2)
        {

            if (expr1 == null)
                return expr2;

            // need to detect whether they use the same
            // parameter instance; if not, they need fixing
            ParameterExpression param = expr1.Parameters[0];
            if (ReferenceEquals(param, expr2.Parameters[0]))
            {
                // simple version
                return Expression.Lambda<Func<T, bool>>(
                    Expression.AndAlso(expr1.Body, expr2.Body), param);
            }
            // otherwise, keep expr1 "as is" and invoke expr2
            return Expression.Lambda<Func<T, bool>>(
                Expression.AndAlso(
                    expr1.Body,
                    Expression.Invoke(expr2, param)), param);
        }


        /// <summary>
        /// Used for combining expressions.  If expr1 is null, expr2 will be returned
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="expr2"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> OrElse<T>(
                this Expression<Func<T, bool>> expr1,
                Expression<Func<T, bool>> expr2)
        {

            if (expr1 == null)
                return expr2;
            // need to detect whether they use the same
            // parameter instance; if not, they need fixing
            ParameterExpression param = expr1.Parameters[0];
            if (ReferenceEquals(param, expr2.Parameters[0]))
            {
                // simple version
                return Expression.Lambda<Func<T, bool>>(
                    Expression.OrElse(expr1.Body, expr2.Body), param);
            }
            // otherwise, keep expr1 "as is" and invoke expr2
            return Expression.Lambda<Func<T, bool>>(
                Expression.OrElse(
                    expr1.Body,
                    Expression.Invoke(expr2, param)), param);
        }     
    }
}*/
}